// test-1.js

"use strict";

function testLog() {
    baseLog("/////////////////////////////////////////////////////////////////");
    baseLog("Hello test-1.js!");
}

// TODO: Make sure how 'use strict' verifies the variables.
function testStrict() {
    let a = 20;
    let b = 10;
    let c = a + b;
    var d = a - b;
}

function testVariable() {
    var message_local = "local message";      // local variable
    // message_global = "global message";         // global variable

    if (true) {
        baseLog("undefined variable %s", name_var);     // undefined
        // baseLog("ReferenceError let variable %s", name_let);        // ReferenceError

        var name_var = 'name_var';      // var variable can work in method area.
        var name_var = 'name_var_duplicate';        // ok
        baseLog("name_var = %s", name_var);
        let name_let = 'name_let';      // let variable can only work in block area.
        // let name_let = 'name_let_duplicate';        // SyntaxError
        baseLog("name_let = %s", name_let);
    }
    baseLog("outside of if block, we can still use %s", name_var);
    try {
        baseLog("outside of if block, the name_let is null.", name_let);
    }
    catch {
        baseLog("catch the name_let exception.")
    }

    for (let i = 0; i < 5; i++)
    {
        // setTimeout(() => baseLog(i), 0);    // output is 0,1,2,3,4.
    }
    for (var i = 0; i < 5; i++)
    {
        // setTimeout(() => baseLog(i), 0);    // output is 5,5,5,5,5 instead of 0,1,2,3,4.
    }

    const val_const = "val_const";      // cannot modified.
}

function testVariableType() {
    ////////// undefined
    let message;
    baseLog(message == undefined);      // true
    baseLog(message);       // undefined
    baseLog(typeof(message));       // undefined
    if (message) {
        baseLog("In if condition, undefined is false.");
    }
    if (!message) {
        baseLog("In if condition, !undefined is true.");
    }

    ////////// null
    let car = null;
    baseLog(typeof(car));       // object
    baseLog(null == undefined);     // true. undefined inherits from null.
    if (car) {
        baseLog("In if condition, null is false.");
    }
    if (!car) {
        baseLog("In if condition, !null is true.");
    }

    ////////// boolean
    
    ////////// number
    let intNum = 55;        // integer
    let hexNum1 = 0xA;          // hex number 10
    let hexNum2 = 0x1f;         // hex number 31
    let floatNum1 = 1.1;        // float number needs double memory than integer.
    let floatNum2 = 3.125e7;    // 31250000
    let floatNum3 = 3e-7;       // 0.0000003
    let floatNum4 = 0.1, floatNum5 = 0.2;
    if (floatNum4 + floatNum5 == 0.3) {
        baseLog("Should not use == to check float number.");
    }
    let numMax = Number.MAX_VALUE;
    let numMin = Number.MIN_VALUE;
    let numPositiveInfinity = Infinity;     // Number.POSITIVE_INFINITY
    let numNegativeInfinity = -Infinity;    // Number.NEGATIVE_INFINITY
    baseLog(0/0);       // NaN, Not a Number.
    baseLog(5/0);       // Infinity
    baseLog(-5/0);      // -Infinity
    baseLog(NaN == NaN);        // false
    baseLog(isNaN(NaN));        // true;
    let numConvert1 = Number("Hello World!");       // NaN
    let numConvert2 = Number("");                   // 0
    let numConvert3 = Number("000011");             // 11
    let numConvert4 = Number(true);                 // 1
    let numParseInt1 = parseInt("1234blue");        // 1234
    let numParseInt2 = parseInt("");                // 0
    let numParseInt3 = parseInt("0xA");             // 10
    let numParseInt4 = parseInt(22.5);              // 22
    let numParseint5 = parseInt("AF", 16);          // 175
    let numParseint6 = parseInt("AF");              // NaN
    let numParseFloat1 = parseFloat("1234blue");    // 1234
    let numParseFloat2 = parseFloat("0xA");         // 0
    let numParseFloat3 = parseFloat("22.5");        // 22.5
    let numParseFloat4 = parseFloat("22.34.5");     // 22.34

    ////////// string
    let stringDefine1 = "string";
    let stringDefine2 = 'string';               // recommend
    let stringDefine3 = `string`;
    let stringLang = `Java`;
    stringLang = stringLang + `Script`;         // All string is immutable! The origin stringLang will be destroyed and assigned with new value "JavaScript".
    let ageNum = 29;
    let ageNumAsString = ageNum.toString();     // "29"
    let boolValue = true;
    let boolValueAsString = boolValue.toString();       // true
    let numBase = 10;
    baseLog(numBase.toString());            // "10"
    baseLog(numBase.toString(2));           // "1010"
    baseLog(numBase.toString(16));          // "a"
    let value1 = 10;
    let value2 = true;
    let value3 = null;
    let value4;
    baseLog(String(value1));            // "10"
    baseLog(String(value2));            // "true"
    baseLog(String(value3));            // "null"
    baseLog(String(value4));            // undefined
    let multiLine1 = `first line\nsecond line`;
    let multiLine2 = `first line
second line`;
    baseLog(multiLine1);
    baseLog(multiLine2);
    baseLog(multiLine1 == multiLine2);      // true
    let spliceNumber = 5;
    let spliceString = `second`;
    let mergedString1 = `This is ` + spliceNumber + ` a merged ` + spliceString + ` string.`;
    let mergedString2 = `This is ${spliceNumber} a merged ${spliceString} string.`;
    baseLog(mergedString1);
    baseLog(mergedString2);
    let selfObj = { toString: () => `World` };
    baseLog(`Hello ${selfObj}!`);
    {
        let a = 6, b = 9;
        function simpleTag(strings, aValExpression, bValExpression, sumExpression) {
            baseLog(strings);
            baseLog(aValExpression);
            baseLog(bValExpression);
            baseLog(sumExpression);
            return `foobar`;
        }
        let untaggedResult = `${a}+${b}=${a+b}`;
        let taggedResult = simpleTag`${a}+${b}=${a+b}`;
        baseLog(untaggedResult);
        baseLog(taggedResult);
        function zipTag(strings, ...expressions) {
            return strings[0] + expressions.map((e, i) => `${e}${strings[i+1]}`).join(``);
        }
        let zippedResult = zipTag`${a}+${b}=${a+b}`;
        baseLog(zippedResult);
    }
    {
        baseLog(`\u00A9`);      // "©"
        baseLog(String.raw`\u00A9`);        // "\u00A9"
        baseLog(String.raw`first line\nsecond line`);       // "first line\nsecond line"
        function printRaw(strings) {
            baseLog(`Actual characters:`);
            for (const str of strings) {
                baseLog(str);
            }
            baseLog(`Escaped characters:`);
            for (const str of strings.raw) {
                baseLog(str);
            }
        }
        printRaw`\u00A9${`and`}\n`;
    }

    ////////// symbol
    {
        let sym = Symbol();
        baseLog(typeof(sym));       // symbol
        baseLog(sym);               // Symbol();
        baseLog(Symbol('foo'));     // Symbol(foo)
        baseLog(Symbol() == Symbol());      // false
        baseLog(Symbol('foo') == Symbol('foo'));        // false
    }
    {
        let myBoolean = new Boolean();
        baseLog(typeof myBoolean);          // "object"
        let myString = new String();
        baseLog(typeof myString);           // "object"
        let myNumber = new Number();
        baseLog(typeof myNumber);           // "object"
        // let mySymbol = new Symbol();            // SyntaxError: Symbol is not a constructor
        let mySymbol = Symbol();
        let myWrappedSymbol = Object(mySymbol);
        baseLog(typeof myWrappedSymbol);        // "object"
    }
    {
        let fooGlobalSymbol = Symbol.for('foo');
        let otherFooGlobalSymbol = Symbol.for('foo');       // reuse fooGlobalSymbol
        baseLog(fooGlobalSymbol == otherFooGlobalSymbol);       // true;
        let localSymbol = Symbol('foo');
        let globalSymbol = Symbol.for('foo');
        baseLog(localSymbol == globalSymbol);           // false
        let emptyGlobalSymbole = Symbol.for();
        baseLog(emptyGlobalSymbole);                // Symbol(undefined)
    }
    {
        let s = Symbol.for('foo');
        baseLog(Symbol.keyFor(s));                  // foo
        let s2 = Symbol('bar');
        baseLog(Symbol.keyFor(s2));                 // undefined
        // Symbol.keyFor(123);                             // TypeError: 123 is not a Symbol.
    }
    {
        let s1 = Symbol('foo');
        let s2 = Symbol('bar');
        let s3 = Symbol('baz');
        let s4 = Symbol('qux');
        let o = {[s1] : 'foo val'};     // The same as: o[s1] = 'foo val';
        baseLog(o);                 // {Symbol(foo): foo val}
        Object.defineProperty(o, s2, {value: 'bar val'});
        baseLog(o);                 // {Symbol(foo): foo val, Symbol(bar): bar val}
        Object.defineProperties(o, {
            [s3]: {value: 'baz val'},
            [s4]: {value: 'qux val'}
        });
        baseLog(o);                 // {Symbol(foo): foo val, Symbol(bar): bar val, Symbol(baz): baz val, Symbol(qux): qux val}
    }
    {
        let s1 = Symbol('foo');
        let s2 = Symbol('bar');
        let o = {
            [s1] : 'foo val',
            [s2] : 'bar val',
            baz : 'baz val',
            qux : 'qux val'
        };
        baseLog(Object.getOwnPropertySymbols(o));           // [Symbol(foo), Symbol(bar)]
        baseLog(Object.getOwnPropertyNames(o));             // ['baz', 'qux']
        baseLog(Object.getOwnPropertyDescriptors(o));       // {baz: {...}, qux: {...}, Symbol(foo): {...}, Symbol(bar): {...}}
        baseLog(Reflect.ownKeys(o));                        // ["baz", "qux", Symbol(foo), Symbol(bar)]
    }
    {
        class Foo {
            async *[Symbol.asyncIterator]() {}
        }
        let f = new Foo();
        baseLog(f[Symbol.asyncIterator]());             // AsyncGenerator {<suspended>}
    }
    {
        class Emitter {
            constructor(max) {
                this.max = max;
                this.idx = 0;
                this.asyncIdx = 0;
            }
            *[Symbol.iterator]() {
                while (this.idx < this.max) {
                    yield this.idx++;
                }
            }
            async *[Symbol.asyncIterator]() {
                while (this.asyncIdx < this.max) {
                    yield new Promise((resolve) => resolve(this.asyncIdx++));
                }
            }
        }
        function count() {
            let emitter = new Emitter(5);
            for (const x of emitter) {
                baseLog(x);             // 0,1,2,3,4
            }
        }
        async function asyncCount() {
            let emitter = new Emitter(5);
            for await (const x of emitter) {
                baseLog(x);
            }
        }
        count();
        // asyncCount();                   // 0,1,2,3,4
    }
    {
        function Foo() {}
        let f = new Foo();
        baseLog(f instanceof Foo);              // true
        baseLog(Foo[Symbol.hasInstance](f));    // true
        class Bar {}
        let b = new Bar();
        baseLog(b instanceof Bar);              // true
        baseLog(Bar[Symbol.hasInstance](b));    // true
        class Baz extends Bar {
            static [Symbol.hasInstance]() {
                return false;
            }
        }
        let bz = new Baz();
        baseLog(Bar[Symbol.hasInstance](bz));       // true
        baseLog(bz instanceof Bar);                 // true
        baseLog(Baz[Symbol.hasInstance](bz));       // false
        baseLog(bz instanceof Baz);                 // false
    }
    {
        baseLog(RegExp.prototype[Symbol.match]);    // f [Symbol.match]() {[native code]}
        baseLog('foobar'.match(/bar/));             // ['bar', index:3, input:'foobar', groups:undefined]
        class FooMatcher {
            static [Symbol.match](target) {
                return target.includes('foo');
            }
        }
        baseLog('foobar'.match(FooMatcher));        // true
        baseLog('barbaz'.match(FooMatcher));        // false
        class StringMatcher {
            constructor(str) {
                this.str = str;
            }
            [Symbol.match](target) {
                return target.includes(this.str);
            }
        }
        baseLog('foobar'.match(new StringMatcher('foo')));      // true
        baseLog('barbaz'.match(new StringMatcher('foo')));      // true
    }
    {
        class Foo {}
        let foo = new Foo();
        baseLog(3 + foo);           // '3[object Object]'
        baseLog(3 - foo);           // NaN
        baseLog(String(foo));       // '[object Object]'
        class Bar {
            constructor() {
                this[Symbol.toPrimitive] = function (hint) {
                    switch (hint) {
                        case 'number':
                            return 3;
                        case 'string':
                            return 'string bar';
                        case 'default':
                        default:
                            return 'default bar';
                    }
                }
            }
        }
        let bar = new Bar();
        baseLog(3 + bar);           // '3default bar'
        baseLog(3 - bar);           // 0
        baseLog(String(bar));       // 'string bar'
    }
}

function testOperators() {
    {
        let s1 = '2';
        let s2 = 'z';
        let b = false;
        let f = 1.1;
        let o = {
            valueOf() {
                return -1;
            }
        };
        s1++;           // 3
        s2++;           // NaN
        b++;            // 1
        f--;            // 0.10000000000009
        o--;            // -2
    }
    {
        let num1 = 25;
        let num2 = ~num1;
        baseLog(num2);           // -26
        let result1 = 25 & 3;
        baseLog(result1);        // 1
        let result2 = 25 | 3;
        baseLog(result2);        // 27
        let result3 = 25 ^ 3;
        baseLog(result3);        // 26
        let value = 2;
        let leftValue = value << 5;         // 64
        let rightValue = leftValue >> 5;    // 2
        value = -64;
        let rightWithNoSymbol = value >>> 5;        // 134217726
    }
    {
        baseLog(!false);            // true
        baseLog(!'blue');           // false
        baseLog(!0);                // true
        baseLog(!NaN);              // true
        baseLog(!'');               // true
        baseLog(!12345);            // false
    }
    {
        let result1 = true && false;
        let result2 = true || false;
    }
    {
        let result1 = 34 * 56;
        let result2 = 34 / 56;
        let result3 = 34 % 5;
        let result4 = Math.pow(3, 2);       // 9, The same as 3 ** 2;
        let result4_2 = 3 ** 2;             // 9
        let squared = 3;
        squared **= 2;      // 9
    }
    {
        baseLog(5 + 5);         // 10
        baseLog(5 + '5');       // '55'
    }
    {
        baseLog('55' == 55);        // true
        baseLog('55' === 55);       // false
    }
    {
        let max = (3 > 4) ? 3 : 4;
    }
}

function testStatements() {
    {
        if (false) {
            baseLog('if condition 1');
        } else if (false) {
            baseLog('if condition 2');
        } else {
            baseLog('if condition 3');
        }
    }
    {
        let i = 0;
        do {
            i += 2;
        } while (i < 10);
        while (i > 0) {
            i -= 2;
        }
        for (let j = 0; j < 10; j++) {
            j++;
        }
        {
            for (const idx in [2,4,6,8]) {
                baseLog(idx);           // 1,2,3,4
            }
            for (const el of [2,4,6,8]) {
                baseLog(el);            // 2,4,6,8
            }
        }
        {
            // label: statement
            let num1 = 0, num2 = 0;
            outermost1:
            for (let i = 0; i < 10; i++) {
                for (let j = 0; j < 10; j++) {
                    if (i == 5 && j == 5) {
                        break outermost1;
                    }
                    num1++;
                }
            }
            baseLog(num1);           // 55
            outermost2:
            for (let i = 0; i < 10; i++) {
                for (let j = 0; j < 10; j++) {
                    if (i == 5 && j == 5) {
                        continue outermost2;
                    }
                    num2++;
                }
            }
            baseLog(num2);          // 95
        }
        {
            // with (expression) statement;
            // cannot used in strict mode.
            /*
            {
                let qs = localtion.search.substring(1);
                let hostName = location.hostname;
                let url = localtion.href;
            }
            {
                with (location) {
                    let qs = search.substring(1);
                    let hostName = hostname;
                    let url = href;
                }
            }
            */
        }
        {
            /*
            switch (expression) {
                case value1:
                    statement1;
                    break;
                case value2:
                    statement2;
                    break;
                default:
                    statement3;
                    break;
            }
            */
        }
    }
}

function testFunctions() {
    {
        /*
        function functionName(arg0, arg1, ..., argN) {
            statements;
        }
        functionName(arg0, arg1, ..., argN);
        */
    }
}

function testValueType() {
    /*
        primitive value: undefined, null, boolean, number, string, symbol
        reference value: object
    */
   {
       let person = new Object();
       person.name = 'myName';
       baseLog(person.name);        // 'myName'
       let name = 'myName';
    //    name.age = 29;                   // TypeError: Cannot create property 'age' on string 'myName'
    //    baseLog(name.age);           // undefined
   }
   {
       function setName(obj) {
           obj.name = 'Name2';
           obj = new Object();
           obj.name = 'Name3';
       }
       let person = new Object();
       person.name = 'Name1';
       setName(person);
       baseLog(person.name);        // 'Name2'
   }
   {
       baseLog(global_value_name);      // undefined
       var global_value_name = 'globalName';
       function setGlobalValueName() {
           baseLog(global_value_name);  // undefined
           var global_value_name = 'globalName2';
       }
   }
   {
       const o1 = {};
    //    o1 = {};             // TypeError
       const o2 = {};
       o2.name = 'Jake';
       baseLog(o2.name);    // 'Jake'
       const o3 = Object.freeze({});
    //    o3.name = 'Bake';            // TypeError
    //    baseLog(o3.name);        // undefined
   }
}

function testDate() {
    {
        let y2k = new Date(Date.UTC(2022,4,1,20,15,33));        // 2022-4-1 20:15:333
        let start = Date.now();
        // do something;
        let stop = Date.now();
        let duration = stop - start;
        baseLog(y2k.toLocaleString());                  // 2022/5/2 04:15:33
        baseLog(y2k.toString());                        // Mon May 02 2022 04:15:33 GMT+0800 (中国标准时间)
    }
}

function testRegExp() {
    {
        let text = 'mom and dad and baby';
        let pattern = /mom( and dad( and baby)?)?/gi;
        let matches = pattern.exec(text);
        baseLog(matches.index);             // 0;
        baseLog(matches.input);             // 'mom and dad and baby'
        baseLog(matches[0]);                // 'mom and dad and baby'
        baseLog(matches[1]);                // ' and dad and baby'
        baseLog(matches[2]);                // ' and baby'
    }
    {
        let text = '000-00-0000';
        let pattern = /\d{3}-\d{2}-\d{4}/;
        if (pattern.test(text)) {
            baseLog('The pattern was matched.');
        }
    }
    {
        let text = 'this has been a short summer';
        let pattern = /(.)hort/g;
        if (pattern.test(text)) {
            baseLog(RegExp.input);          // 'this has been a short summer'
            baseLog(RegExp.leftContext);    // 'this has been a'
            baseLog(RegExp.rightContext);     // ' summer'
            baseLog(RegExp.lastMatch);      // 'short'
            baseLog(RegExp.lastParen);      // 's'
        }
    }
}

function testOriginValueWrapper() {
    {
        let value = '25';
        let num = Number(value);
        baseLog(typeof num);        // 'number'
        let obj = new Number(value);
        baseLog(typeof obj);        // 'object'
    }
    {
        let falseObject = new Boolean(false);
        let result = falseObject && true;
        baseLog(result);                        // true
        let falseValue = false;
        result = falseValue && true;
        baseLog(result);                        // false
        baseLog(typeof falseObject);            // object
        baseLog(typeof falseValue);             // boolean
        baseLog(falseObject instanceof Boolean);        // true;
        baseLog(falseValue instanceof Boolean);         // false
    }
    {
        let num = 10;
        baseLog(num.toFixed(2));            // '10.00'
        let num2 = 10.005;
        baseLog(num2.toFixed(2));           // '10.01'
        let num3 = 1000;
        baseLog(num3.toExponential(1));     // '1.0e+3'
        let num4 = 99;
        baseLog(num4.toPrecision(1));       // '1e+2'
        baseLog(num4.toPrecision(2));       // '99'
        baseLog(num4.toPrecision(3));       // '99.0'
    }
    {
        baseLog(Number.isSafeInteger(-1 * (2 ** 53)));              // false
        baseLog(Number.isSafeInteger(-1 * (2 ** 53) + 1));          // true;
        baseLog(Number.isSafeInteger(2 ** 53));                     // false;
        baseLog(Number.isSafeInteger((2 ** 53) - 1));               // true
    }
    {
        let message = 'abcde';
        baseLog(message.length);                    // 5
        baseLog(message.charAt(2));                 // 'c'
        baseLog(message.charCodeAt(2));             // 99
        baseLog(99 == 0x63);                        // true
        baseLog(String.fromCharCode(97, 98, 99, 100, 101));         // 'abcde'
    }
    {
        let s1 = 'hello';
        let s2 = s1.concat(' world', '!');
        let s3 = s1 + ' world' + '!';
        baseLog(s2);                        // 'hello world!'
        baseLog(s3);                        // 'hello world!'
        baseLog(s2.substring(2));           // 'llo world!'
        baseLog(s2.substring(2, 4));        // 'll'
        let message = 'foobarbaz';
        baseLog(message.startsWith('foo'));         // true
        baseLog(message.startsWith('bar'));         // false
        baseLog(message.endsWith('baz'));           // true
        baseLog(message.endsWith('bar'));           // false
        baseLog(message.includes('bar'));           // true
        baseLog(message.includes('qux'));           // false
        let ss = '     remove space in head and   tail   ';
        let ss2 = ss.trim();
        baseLog(ss2);                               // 'remove space in head and   tail'
        let sss = 'foo';
        baseLog(sss.padStart(6));                   // '   foo'
        baseLog(sss.padStart(6, '.'));              // '...foo'
        baseLog(sss.padEnd(6));                     // 'foo   '
        baseLog(sss.padEnd(6, 'de'));               // 'fooded'
    }
    {
        let message = 'abc';
        let iter = message[Symbol.iterator]();
        baseLog(iter.next());                       // {value: 'a', done: false}
        baseLog(iter.next());                       // {value: 'b', done: false}
        baseLog(iter.next());                       // {value: 'c', done: false}
        baseLog(iter.next());                       // {value: undefined, done: true}
        for (const c of 'abcd') {
            baseLog(c);                             // 'a' 'b' 'c' 'd'
        }
        baseLog([...message]);                      // ['a', 'b', 'c']
    }
    {
        let text = 'cat, bat, sat, fat';
        let pattern = /.at/;
        let matches = text.match(pattern);              // The same as pattern.exec(text);
        baseLog(matches.index);                     // 0
        baseLog(matches[0]);                        // 'cat'
        baseLog(pattern.lastIndex);                 // 0
        let pos = text.search(/at/);
        baseLog(pos);                               // 1
        let r1 = text.replace('at', 'ond');
        baseLog(r1);                                // 'cond, bat, sat, fat'
        let r2 = text.replace(/at/g, 'ond');
        baseLog(r2);                                // 'cond, bond, sond, fond'
        let r3 = text.replace(/(.at)/g, 'word($1)');
        baseLog(r3);                                // 'word(cat), word(bat), word(sat), word(fat)
        function htmlEscape(text) {
            return text.replace(/[<>"&]/g, function(match, pos, originalText) {
                switch (match) {
                    case '<':
                        return '&lt; ';
                    case '>':
                        return '&gt; ';
                    case '&':
                        return '&amp; ';
                    case '\"':
                        return '&quot; ';
                }
            });
        }
        baseLog(htmlEscape('<p class=\"greeting\"Hello World! </p>'));          // &lt; p class=&quot; greeting&quot; Hello World! &lt; /p&gt; 
    }
    {
        let colorText = 'red, blue, green, yellow';
        let colors1 = colorText.split(', ');                    // ['red', 'blue', 'green', 'yellow'];
        let colors2 = colorText.split(', ', 2);                 // ['red', 'blue']
        let colors3 = colorText.split(/[^, ]+/);                // ['', ', ', ', ', ', ', '']
        baseLog(colors1);
        baseLog(colors2);
        baseLog(colors3);
    }
    {
        let s = 'yellow';
        baseLog(s.localeCompare('brick'));              // 1
        baseLog(s.localeCompare('yellow'));             // 0
        baseLog(s.localeCompare('zoo'));                // -1
    }
}

function testSingleBuiltinObject() {
    {
        let uri = 'http://www.wrox.com/illegal value.js#start';
        baseLog(encodeURI(uri));                            // http://www.wrox.com/illegal%20value.js#start
        baseLog(encodeURIComponent(uri));                   // http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start
    }
    {
        let msg = 'msg';
        // eval('baseLog(msg)');                   // 'msg'
        eval("let msgInEval = 'msgInEval';");
        // baseLog(msgInEval);                     // Reference Error: msgInEval is not defined
    }
    {
        /*
            attributes that belong to Global:
            undefined, NaN, Infinity, Object, Arrary, Function, Boolean, String, Number, Date, RegExp, Symbol,
            Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError.
        */
    }
    {
        var color = 'red';
        function sayColor() {
            baseLog(window.color);
        }
        // window.testSingleBuiltinObject().sayColor();              // 'red',   will cause recrusive call.
    }
    {
        // method to get global object
        /*
            let global = function() {
                return this;
            }();
        */
    }
    {
        let max = Math.max(3, 523, 23, 22);             // 523
        let min = Math.min(3, 523, 23, 22);             // 3
        let arr = [1, 2, 3, 4];
        let maxInArr = Math.max(...arr);                // 4
        baseLog(Math.ceil(25.9));                   // 26
        baseLog(Math.round(25.1));                  // 25
        baseLog(Math.round(25.8));                  // 26
        baseLog(Math.floor(25.8));                  // 25
        baseLog(Math.floor(Math.random() * 100 + 1));                       // [1, 101)
    }
}


///////////////////////////////////////////////////////////////////////////////////////
testLog();
testStrict();
testVariable();
// baseLog(message_global);        // actually cannot access it.
var var_global = "var_global";
baseLog("%s can be accessed through window property.", window.var_global);
let let_global = "let_global";
baseLog("%s cannot be accessed through window property.", window.let_global);
testVariableType();
testOperators();
testStatements();
testFunctions();
testValueType();
testDate();
testRegExp();
testOriginValueWrapper();
testSingleBuiltinObject();
